#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" 
Google Apps Provisioning API is here
http://code.google.com/intl/zh-TW/apis/apps/gdata_provisioning_api_v2.0_reference_python.html 
We can use libgmail api to send email if necessar. But send mail in unix is pretty simple, so I skip this
http://libgmail.sourceforge.net/
How to Install Python Library 
http://code.google.com/intl/zh-TW/apis/gdata/articles/python_client_lib.html
Python GData API doc is here
http://gdata-python-client.googlecode.com/svn/trunk/pydocs/gdata.html
Provisioning API source code is here
http://code.google.com/p/gdata-python-client/source/browse/trunk/src/gdata/apps/service.py?r=584


Examples: 

  The CSV file must have this at header'username ,first name ,last name ,password'
  NOTE: the first line of input/output cvs will always be the header not part of data

Read input cvs with user name only and output out.cvs which generate first time password
  ./gapp_account_mgr.py --user abc -i account_nameonly.csv -a create_user -o out.csv

Read input cvs with user name only and just generate CVS file
  ./gapp_account_mgr.py --user abc -i account_nameonly.csv -a generate_csv -o out.csv

input file don't have password, and output generate it.
  ./gapp_account_mgr.py --user abc -i account_autopw.csv -a create_user -o out.csv

Create account exactly follow input account/password infomation
  ./gapp_account_mgr.py --user abc -i account.csv -a create_user

Suspend user in list file
  ./gapp_account_mgr.py --user abc -i account_nameonly.csv -a suspend_user

Restore user in list file
  ./gapp_account_mgr.py --user abc -i account_nameonly.csv -a restore_user

"""
import warnings
warnings.filterwarnings('ignore', '.*')
import logging
import getpass, string
from optparse import OptionParser
from xml.etree import ElementTree
import gdata.apps.service
from gdata.apps.service import AppsForYourDomainException
import random

user_create_count = 0
user_update_count = 0
user_fail_count = 0
user_fail_list = []
outputfile = None
service = None

def main():
  global user_create_count
  global user_update_count
  global user_fail_count
  global user_fail_list
  global outputfile
  global service

  usage = "usage: %prog [options] arg"
  parser = OptionParser(usage)
  parser.add_option("-i", "--inputfile", dest="inputfile",
      help="read input data from INPUTFILE")
  parser.add_option("-o", "--outputfile", dest="outputfile",
      help="write out data to OUTPUTFILE for generated password")
  parser.add_option("-s", "--suspended", help="suspend user account",
      action="store_true", dest="suspended", default=False)
  parser.add_option("-c", "--change_password", help="request user change password next time",
      action="store_true", dest="change_password", default=False)
  parser.add_option("-v", "--verbose",
      action="store_true", dest="verbose", default=True)
  parser.add_option("-q", "--quiet",
      action="store_false", dest="verbose")
  parser.add_option("-u", "--user", dest="user",
      help="user name for login Google Apps")
  parser.add_option("-d", "--domain", dest="domain",
      help="domain name for Google Apps", default='ncnu.edu.tw')
  parser.add_option("-a", "--action", dest="action",
      help="ACTION type: create_user, update_user, generate_csv suspend_user restore_user", default='create_user')
  parser.add_option("-l", "--password_length", dest="password_length",
      help="Password length for autogenerate password", default=12)

  (options, args) = parser.parse_args()
  #if len(args) != 1:
      #parser.error("incorrect number of arguments")

  required_options = ['inputfile', 'user', 'domain']

  for op in required_options:
    if not hasattr(options, op) or None == getattr(options, op):
        parser.error("--%s is must option." % (op,))

  if options.action == 'generate_csv' and (not options.outputfile):
    parser.error("--outputfile is must option when action is generate_csv.")
  
  if options.outputfile:
    outputfile = open(options.outputfile, 'w')

  if options.action != 'generate_csv':
    #passwd = raw_input("please input password for '%s':" (options.user,))
    mypasswd = getpass.getpass("password for Google Apps ('%s'):" % (options.domain,))
    #print "login wiht password = '%s'" % (mypasswd,)
    email = "%s@%s" % (options.user,options.domain)
    print "Login using email account " + email
    service = gdata.apps.service.AppsService(email=email, domain=options.domain, password=mypasswd)
    service.ProgrammaticLogin()

  if options.verbose:
    print "reading %s..." % options.inputfile
  if options.verbose and options.outputfile:
    print "writing %s..." % options.outputfile
  
  funcs = globals()
  func = funcs.get(options.action)
  if not func:
    parser.error("there is not such action '%s' type support." % (options.action))

  if outputfile and ( options.action == 'generate_csv' or options.action == 'create_user'):
    outputfile.write("username ,first name ,last name ,password\n")

  line_count = 0;
  user_create_count = 0
  user_update_count = 0
  user_fail_count = 0
  user_fail_list = []
  for line in open(options.inputfile, 'r'):
    #func = getattr(self.methods, options.action, None)
    line_count = line_count+1
    if line_count == 1:
      headers = string.split(line, ',')
      #print "username '%s' first '%s' last '%s' password '%s'" % (headers[0], headers[1], headers[2], headers[3])
      if headers[0].strip() != 'username' or headers[1].strip() != 'first name' or headers[2].strip() != 'last name' or headers[3].strip() != 'password':
        print "first line of INPUTFILE must like this"
        print "'username ,first name ,last name ,password'"
      continue
    #print "call function= %s" % (options.action)
    strs = string.split(line, ',')
    username = None
    first_name = None
    last_name = None
    password = None
    if len(strs) > 0 : username = strs[0].strip()
    if len(strs) > 1 : first_name = strs[1].strip()
    if len(strs) > 2 : last_name = strs[2].strip()
    if len(strs) > 3 : password = strs[3].strip()
    result = func(options=options, username=username, first_name=first_name, last_name=last_name, password=password)
  
  if user_create_count > 0:
    print "total %d user account are created" % (user_create_count)
  if user_update_count > 0:
    print "total %d user account are updated" % (user_update_count)
    
  if user_fail_count > 0 :
    print "ERROR: following user is fail to %s account: %s" % (options.action, string.join(user_fail_list, ','))

  if outputfile:
    outputfile.close()

def generate_csv(options, username, first_name, last_name, password):
  if not password:
    password = GeneratePasswordBase64(username+str(random.random()), options.password_length)
  if not first_name : first_name = ""
  if not last_name : last_name= ""
    
  outputfile.write("%s,%s,%s,%s\n" %(username, first_name, last_name, password))
  return

def create_user(options, username, first_name, last_name, password):
  global user_create_count
  global user_fail_count
  global user_fail_list
  global outputfile
  global service

  #print "call create_user, to create %s@%s account: who is '%s %s' " % (username, options.domain, first_name, last_name)
  if not password:
    password = GeneratePasswordBase64(username+options.domain, options.password_length)
    #print "password is not give, auto generate it as '%s'" %(password)

  if outputfile:
    generate_csv(options, username, first_name, last_name, password)

  try:
    service.CreateUser(user_name=username, family_name=last_name, given_name=first_name, password=password)
    user_create_count = user_create_count + 1
  except AppsForYourDomainException:
    user_fail_count = user_fail_count + 1
    user_fail_list += [username]

def update_user(options, username, first_name, last_name, password):
  global user_update_count
  global user_fail_count
  global user_fail_list
  global outputfile
  global service

  #print "call create_user, to create %s@%s account: who is '%s %s' " % (username, options.domain, first_name, last_name)
  if not password:
    password = GeneratePasswordBase64(username+options.domain, options.password_length)
    #print "password is not give, auto generate it as '%s'" %(password)

  try:
    userentry = service.RetrieveUser(user_name=username)
    userentry.name = gdata.apps.Name(family_name=last_name, given_name=first_name)


    change_password = userentry.login.change_password
    suspended = userentry.login.suspended

    if options.change_password:
      change_password = 'true'
    else:
      change_password = 'false'

    if options.suspended:
      suspended = 'true'
    else:
      suspended = 'false'

    userentry.login = gdata.apps.Login(user_name=username, password=password, suspended=suspended,hash_function_name=None, change_password=change_password)
                        
    service.UpdateUser(user_name=username, user_entry=userentry)
    if outputfile:
      generate_csv(options, username, first_name, last_name, password)

    user_update_count = user_update_count + 1
  except AppsForYourDomainException:
    user_fail_count = user_fail_count + 1
    user_fail_list += [username]

def suspend_user(options, username, first_name, last_name, password):
  global user_update_count
  global user_fail_count
  global user_fail_list
  global service

  try:
    service.SuspendUser(user_name=username)
    user_update_count = user_update_count + 1
  except AppsForYourDomainException:
    user_fail_count = user_fail_count + 1
    user_fail_list += [username]

def restore_user(options, username, first_name, last_name, password):
  global user_update_count
  global user_fail_count
  global user_fail_list
  global service

  try:
    service.RestoreUser(user_name=username)
    user_update_count = user_update_count + 1
  except AppsForYourDomainException:
    user_fail_count = user_fail_count + 1
    user_fail_list += [username]

def GeneratePasswordBase64(passString, passLength):
  """This function creates a SHA-1 hash from the passString. The 40 nibbles of
  this hash are expressed in base64 format and the first passLength characters
  are returned as the generated password"""
  assert passLength <= 160/6  #because each base64 character is derived from 6 bits

  import base64
  if False :
    #for order then python 2.5
    import sha
    return base64.b64encode(sha.sha(passString).hexdigest())[:passLength]
  else:
    import hashlib 
    sha = hashlib.sha1()
    sha.update(passString)
    return base64.b64encode(sha.hexdigest())[:passLength].lower()


if __name__ == "__main__":
  main()

